package com.xjoyt.pay.master.services;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.client.RestTemplate;

import com.xjoyt.pay.abstracts.AbstractService;
import com.xjoyt.pay.annotations.LockMethod;
import com.xjoyt.pay.annotations.LockParam;
import com.xjoyt.pay.commons.ObjectUtils;
import com.xjoyt.pay.dto.ExecuteResult;
import com.xjoyt.pay.dto.PageParam;
import com.xjoyt.pay.dto.PageResult;
import com.xjoyt.pay.gateway.params.CashParam;
import com.xjoyt.pay.master.dto.CashResult;
import com.xjoyt.pay.master.interfaces.ChannelContract;
import com.xjoyt.pay.master.interfaces.MerchantContract;
import com.xjoyt.pay.master.params.CashAdapterParam;
import com.xjoyt.pay.master.params.CashDetailPageParam;
import com.xjoyt.pay.master.params.MerchantCashPageParam;
import com.xjoyt.pay.master.po.Cash;
import com.xjoyt.pay.master.po.CashDetail;
import com.xjoyt.pay.master.po.Channel;
import com.xjoyt.pay.master.po.DistributorCashRate;
import com.xjoyt.pay.master.po.MerchantChannel;
import com.xjoyt.pay.master.po.MerchantChannelCashRate;
import com.xjoyt.pay.master.po.MerchantMoney;
import com.xjoyt.pay.master.po.Order;
import com.xjoyt.pay.master.repository.CashDetailRepository;
import com.xjoyt.pay.master.repository.CashRepository;
import com.xjoyt.pay.master.repository.ChannelRepository;
import com.xjoyt.pay.master.repository.DistributorCashRateRepository;
import com.xjoyt.pay.master.repository.MerchantChannelCashRateRepository;
import com.xjoyt.pay.master.repository.MerchantChannelRepository;
import com.xjoyt.pay.master.repository.MerchantMoneyRepository;
import com.xjoyt.pay.master.repository.OrderRepository;
import com.xjoyt.pay.master.vo.CashMap;
import com.xjoyt.pay.vo.MoneyType;

@Service
public class CashService extends AbstractService {

	@Autowired
	private CashRepository repository;

	@Autowired
	private CashDetailRepository cashDetailRepository;

	@Autowired
	private MerchantContract merchantContract;

	@Autowired
	private ChannelContract channelContract;

	@Autowired
	private OrderRepository orderRepository;

	@Autowired
	private DistributorCashRateRepository distributorCashRateRepository;

	@Autowired
	private MerchantChannelRepository merchantChannelRepository;

	@Autowired
	private MerchantChannelCashRateRepository merchantChannelCashRateRepository;

	@Autowired
	private ChannelRepository channelRepository;

	@Autowired
	private MerchantMoneyRepository merchantMoneyRepository;

	@Autowired
	private RestTemplate restTemplate;

	public PageResult<CashMap> loadPage(PageParam<MerchantCashPageParam> param) {
		PageResult<Cash> page = repository.loadPage(param);
		List<CashMap> list = new ArrayList<>();
		List<String> cashIds = new ArrayList<>();
		for (Cash item : page) {
			cashIds.add(item.id);
			CashMap vo = new CashMap();
			list.add(vo);
			ObjectUtils.fieldCopy(item, vo);
		}

		if (CollectionUtils.isNotEmpty(cashIds)) {
			List<Map<String, Object>> sumList = cashDetailRepository.sum(cashIds);
			if (CollectionUtils.isNotEmpty(sumList)) {
				for (Map<String, Object> map : sumList) {
					if (map == null || !map.containsKey("id") || map.get("id") == null) {
						continue;
					}
					Object cashId = map.get("id");
					for (CashMap item : list) {
						if (cashId.equals(item.id)) {
							item.cashMoney = (map.containsKey("cashMoney") && map.get("cashMoney") != null)
									? Double.valueOf(map.get("cashMoney").toString()) : 0D;
							item.poundage = (map.containsKey("poundageSum") && map.get("poundageSum") != null)
									? Double.valueOf(map.get("poundageSum").toString()) : 0D;
						}
					}
				}
			}
		}

		return new PageResult<>(page.getTotal(), list);
	}

	public PageResult<com.xjoyt.pay.master.vo.CashDetail> loadPageMap(PageParam<CashDetailPageParam> param) {
		PageResult<CashDetail> page = cashDetailRepository.loadPageMap(param);
		List<com.xjoyt.pay.master.vo.CashDetail> list = new ArrayList<>();
		List<String> cashIds = new ArrayList<>();

		for (CashDetail item : page) {
			cashIds.add(item.cash.id);
			com.xjoyt.pay.master.vo.CashDetail vo = new com.xjoyt.pay.master.vo.CashDetail();
			list.add(vo);
			ObjectUtils.fieldCopy(item, vo);
		}

		List<Object> footer = new ArrayList<>();

		if (CollectionUtils.isNotEmpty(cashIds)) {
			List<Map<String, Object>> sumList = cashDetailRepository.sum(cashIds);
			if (CollectionUtils.isNotEmpty(sumList)) {
				for (Map<String, Object> map : sumList) {
					if (map == null || !map.containsKey("id") || map.get("id") == null) {
						continue;
					}
					Map<String, Object> map1 = new HashMap<>();
					map1.put("cashIds", (map.containsKey("id") && map.get("id") != null) ? map.get("id") : 0);
					map1.put("moneySum",
							(map.containsKey("moneySum") && map.get("moneySum") != null) ? map.get("moneySum") : 0);
					map1.put("cashMoney",
							(map.containsKey("cashMoney") && map.get("cashMoney") != null) ? map.get("cashMoney") : 0);
					map1.put("poundageSum", (map.containsKey("poundageSum") && map.get("poundageSum") != null)
							? map.get("poundageSum") : 0);
					footer.add(map1);
				}
			}
		}

		return new PageResult<>(page.getTotal(), list, footer);
	}

	public ExecuteResult<Cash> create(CashParam param) {
		if (StringUtils.isNotBlank(param.merchantId) && CollectionUtils.isNotEmpty(param.orders)
				&& param.orders.size() > 0) {

			List<Order> orderList = new ArrayList<>();
			Long cashMoney = 0L;
			for (String item : param.orders) {
				Order order = orderRepository.findAllByNo(item);
				if (order == null) {
					return ExecuteResult.fail(String.format("提现订单号与实际订单号[%s]不符", item));
				}
				if (BooleanUtils.isNotTrue(order.success)) {
					return ExecuteResult.fail(String.format("订单[%s]下单失败，无法提现", item));
				}
				if (BooleanUtils.isNotTrue(order.done)) {
					return ExecuteResult.fail(String.format("订单[%s]未付款，无法提现", item));
				}
				if (BooleanUtils.isTrue(order.hasCash)) {
					return ExecuteResult.fail(String.format("订单[%s]已提现，不可再次提现", item));
				}
				if (BooleanUtils.isTrue(order.back)) {
					return ExecuteResult.fail(String.format("订单[%s]已退款，无法提现", item));
				}
				if (BooleanUtils.isTrue(order.pay)) {
					return ExecuteResult.fail(String.format("订单[%s]已结算，无法提现", item));
				}
				if (!order.merchant.id.equals(param.merchantId)) {
					return ExecuteResult.fail("订单商户与提现商户不符");
				}
				cashMoney += order.money;
				orderList.add(order);
			}
			Cash entity = new Cash();
			if (entity.addedDate == null)
				entity.addedDate = new Date();
			entity.no = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS");
			for (int i = 0; i < 10; i++) {
				entity.no += String.valueOf((int) (Math.random() * 10));
			}
			entity.outTradeNo = param.outTradeNo;
			entity.merchant = merchantContract.get(param.merchantId);
			entity.money = cashMoney;
			entity.merchantNo = merchantContract.get(param.merchantId).no;
			entity.merchantName = merchantContract.get(param.merchantId).name;
			entity.check = null;
			entity = repository.save(entity);

			long limit = 0L, minimum = 0L;
			double rate = 0D;
			if (CollectionUtils.isNotEmpty(orderList)) {
				for (Order order : orderList) {
					Channel channel = channelRepository.findOne(order.orderConfig.channel.id);
					DistributorCashRate distributorCashRate = distributorCashRateRepository
							.findOneByDistributorId(order.merchant.distributor.id);
					MerchantChannel merchantChannel = merchantChannelRepository
							.findOneByMerchantIdAndChannelIdAndEnabledTrue(param.merchantId,
									order.orderConfig.channel.id);
					MerchantChannelCashRate merchantChannelCashRate = merchantChannelCashRateRepository
							.findOneByMerchantChannelId(String.valueOf(merchantChannel.id));

					CashDetail cashDetail = new CashDetail();
					cashDetail.no = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS");
					for (int i = 0; i < 10; i++) {
						cashDetail.no += String.valueOf((int) (Math.random() * 10));
					}
					cashDetail.tradeNo = param.outTradeNo;
					cashDetail.cashType = null;
					cashDetail.cash = entity;
					cashDetail.order = order;
					cashDetail.orderNo = order.no;
					cashDetail.channel = order.orderConfig.channel;
					cashDetail.channelNo = order.orderConfig.channelNo;
					cashDetail.channelName = order.orderConfig.channelName;
					cashDetail.channelRate = channel.cashRate == null ? 0D : channel.cashRate / 100;
					cashDetail.channelPoundage = channel.cashMoney == null ? 0L : channel.cashMoney;
					cashDetail.channelLimit = channel.cashLimit == null ? 0L : channel.cashLimit;
					cashDetail.channelMinimum = channel.cashMinimum == null ? 0L : channel.cashMinimum;
					cashDetail.costRate = distributorCashRate.rate == null ? 0D : distributorCashRate.rate / 100;
					cashDetail.costPoundage = distributorCashRate.money == null ? 0L : distributorCashRate.money;
					cashDetail.costLimit = distributorCashRate.limit == null ? 0L : distributorCashRate.limit;
					cashDetail.costMinimum = distributorCashRate.minimum == null ? 0L : distributorCashRate.minimum;
					cashDetail.poundage = merchantChannelCashRate.money == null ? 0L : merchantChannelCashRate.money;
					cashDetail.money = order.money == null ? 0L : order.money;
					cashDetail.url = channel.cashUrl;
					limit = merchantChannelCashRate.limit == null ? 0L : merchantChannelCashRate.limit;
					minimum = merchantChannelCashRate.minimum == null ? 0L : merchantChannelCashRate.minimum;
					rate = merchantChannelCashRate.rate == null ? 0D : merchantChannelCashRate.rate / 100;

					if (limit > 0 && cashDetail.money * rate >= limit) {
						cashDetail.rate = 0D;
						cashDetail.limit = limit;
						cashDetail.minimum = 0L;
					} else if (minimum > 0 && cashDetail.money * rate <= minimum) {
						cashDetail.rate = 0D;
						cashDetail.limit = 0L;
						cashDetail.minimum = minimum;
					} else {
						cashDetail.rate = rate;
						cashDetail.limit = 0L;
						cashDetail.minimum = 0L;
					}
					cashDetail.cashMoney = order.money - (long) (order.money * cashDetail.rate + cashDetail.poundage
							+ cashDetail.limit + cashDetail.minimum);

					cashDetailRepository.save(cashDetail);
				}
			}

		}

		return ExecuteResult.ok();
	}

	private void toCashMoney(String merchantId, String channelId, long money) {
		MerchantMoney merchantMoney = new MerchantMoney();
		merchantMoney.addedDate = new Date();
		merchantMoney.no = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS");
		for (int i = 0; i < 10; i++) {
			merchantMoney.no += String.valueOf((int) (Math.random() * 10));
		}
		merchantMoney.channel = channelContract.get(channelId);
		merchantMoney.merchant = merchantContract.get(merchantId);
		merchantMoney.money = -money;
		merchantMoney.type = MoneyType.CASH;
		merchantMoneyRepository.save(merchantMoney);
	}

	private void toCashMoneyPoundage(String merchantId, String channelId, long money) {
		MerchantMoney merchantMoney = new MerchantMoney();
		merchantMoney.addedDate = new Date();
		merchantMoney.no = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS");
		for (int i = 0; i < 10; i++) {
			merchantMoney.no += String.valueOf((int) (Math.random() * 10));
		}
		merchantMoney.channel = channelContract.get(channelId);
		merchantMoney.merchant = merchantContract.get(merchantId);
		merchantMoney.money = -money;
		merchantMoney.type = MoneyType.CASH_POUNDAGE;
		merchantMoneyRepository.save(merchantMoney);
	}

	@LockMethod
	private ExecuteResult<Cash> setCheck(@LockParam("id") Cash entity) {
		entity.check = true;
		if (CollectionUtils.isEmpty(entity.details)) {
			return ExecuteResult.fail("无提现明细");
		}
		repository.save(entity);
		List<String> cashDetailList = new ArrayList<>();
		for (CashDetail item : entity.details) {
			item.success = true;
			long poundage = (long) (item.money * item.rate + item.poundage + item.limit + item.minimum);
			if (StringUtils.isBlank(item.url)) {
				item.cashType = true;
				item.pay = false;
			} else {
				item.cashType = false;
				item.pay = null;
				cashDetailList.add(item.id);
			}
			cashDetailRepository.save(item);
			toCashMoney(entity.merchant.id, item.channel.id, item.cashMoney);
			toCashMoneyPoundage(entity.merchant.id, item.channel.id, poundage);
		}
		ExecutorService threadPool = Executors.newCachedThreadPool();
		FutureTask<Object> future = new FutureTask<>(() -> {
			// 异步发送
			cashDetailList.forEach(item -> send(item));
			return null;
		});
		threadPool.execute(future);
		return ExecuteResult.ok(entity);
	}

	public ExecuteResult<Cash> check(@PathVariable("id") String id, @PathVariable("check") Boolean check) {
		Cash entity = repository.findOne(id);
		if (entity.check != null) {
			return ExecuteResult.fail("审核失败");
		}
		if (BooleanUtils.isNotTrue(check)) {
			entity.check = false;
			repository.save(entity);
			return ExecuteResult.ok(entity);
		}
		return setCheck(entity);
		// if (CollectionUtils.isNotEmpty(cashDetailList)) {
		// com.xjoyt.pay.master.params.Cash cashVo = new
		// com.xjoyt.pay.master.params.Cash();
		// ObjectUtils.fieldCopy(entity, cashVo);
		// for (CashDetail item : cashDetailList) {
		// MerchantChannel merchantChannel = merchantChannelRepository
		// .findOneByMerchantIdAndChannelId(entity.merchant.id,
		// item.channel.id);
		// ExecutorService threadPool = Executors.newCachedThreadPool();
		// FutureTask<Object> future = new FutureTask<>(() -> {
		// sendRequest(item, cashVo);
		// return null;
		// });
		// threadPool.execute(future);
		// }
		// }
		// return ExecuteResult.ok();
	}

	// private void sendRequest(CashDetail cashDetail,
	// com.xjoyt.pay.master.params.Cash cash) {
	// try {
	// com.xjoyt.pay.master.params.CashDetail cashDetailVo = new
	// com.xjoyt.pay.master.params.CashDetail();
	// ObjectUtils.fieldCopy(cashDetail, cashDetailVo);
	//
	// CashAdapterParam param = new CashAdapterParam();
	// param.cash = cash;
	// // param.cash.details=null;
	// param.cashDetail = cashDetailVo;
	// // param.merchantChannel = merchantChannel;
	// // Gson gson = new Gson();
	// // String json = gson.toJson(param);
	// // System.out.println("json:------111111-----" + json);
	// ExecutorService threadPool = Executors.newCachedThreadPool();
	// FutureTask<Object> future = new FutureTask<>(() -> {
	// request(cashDetailVo, param);
	// return null;
	// });
	// threadPool.execute(future);
	// } catch (Exception e) {
	// CashDetail cashDetail1 = cashDetailRepository.getOne(cashDetail.id);
	// cashDetail1.pay = false;
	// cashDetailRepository.save(cashDetail1);
	// e.printStackTrace();
	// logger.error("提现服务器异常", e);
	// }
	// }
	//
	// private void request(com.xjoyt.pay.master.params.CashDetail cashDetail,
	// CashAdapterParam param) {
	// CashResult result = restTemplate.postForObject(cashDetail.url, param,
	// CashResult.class);
	// cashDetail.pay = result.success;
	// if (StringUtils.isNotEmpty(result.tradeNo)) {
	// cashDetail.tradeNo = result.tradeNo;
	// }
	// // cashDetailRepository.save(cashDetail);
	// }

	/**
	 * 单笔提现
	 * 
	 * @param detailId
	 * @return
	 */
	@LockMethod
	public ExecuteResult<CashDetail> send(@LockParam String detailId) {
		CashDetail detail = cashDetailRepository.findOne(detailId);
		if (detail == null) {
			return ExecuteResult.fail("数据不存在");
		}
		if (BooleanUtils.isNotFalse(detail.cashType)) {
			return ExecuteResult.fail("提现类型不正确");
		}
		if (BooleanUtils.isTrue(detail.pay)) {
			return ExecuteResult.fail("无法重复提现");
		}
		if (StringUtils.isBlank(detail.url)) {
			return ExecuteResult.fail("无提现适配器URL");
		}
		if (detail.cash == null) {
			return ExecuteResult.fail("无提现申请记录");
		}
		if (BooleanUtils.isNotTrue(detail.cash.check)) {
			return ExecuteResult.fail("提现申请不通过");
		}
		if (detail.order == null) {
			return ExecuteResult.fail("无订单记录");
		}
		if (BooleanUtils.isNotTrue(detail.order.success)) {
			return ExecuteResult.fail("订单下单失败，无法提现");
		}
		if (BooleanUtils.isNotTrue(detail.order.done)) {
			return ExecuteResult.fail("订单未付款，无法提现");
		}
		if (BooleanUtils.isTrue(detail.order.back)) {
			return ExecuteResult.fail("订单已退款");
		}
		if (BooleanUtils.isTrue(detail.order.hasCash)) {
			return ExecuteResult.fail("订单已提现");
		}
		CashAdapterParam param = new CashAdapterParam();
		MerchantChannel merchantChannel = merchantChannelRepository
				.findOneByMerchantIdAndChannelIdAndEnabledTrue(detail.cash.merchant.id, detail.channel.id);
		param.cash = new com.xjoyt.pay.master.params.Cash();
		ObjectUtils.fieldCopy(detail.cash, param.cash);
		param.cashDetail = new com.xjoyt.pay.master.params.CashDetail();
		ObjectUtils.fieldCopy(detail, param.cashDetail);
		param.merchantChannel = new com.xjoyt.pay.master.params.MerchantChannel();
		ObjectUtils.fieldCopy(merchantChannel, param.merchantChannel);
		try {
			CashResult result = restTemplate.postForObject(detail.url, param, CashResult.class);
			detail.pay = result.success;
			if (result.success) {
				detail.tradeNo = result.tradeNo;
				detail.addedDate = new Date();
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("提现适配器异常", e);
			detail.pay = false;
		}
		cashDetailRepository.save(detail);
		return BooleanUtils.isTrue(detail.pay) ? ExecuteResult.ok() : ExecuteResult.fail("提现失败");
	}
}
